package org.codetranslators.compiler.intermrep;

import org.codetranslators.common.Temp;

import java.util.Vector;

public class Operation implements AssemblyInstruction
{
	public static final int ADD = 0;
	public static final int MINUS = 1;
	public static final int MUL = 2;
	public static final int DIV = 3;
	public static final int LOAD_IMMEDIATE_VALUE = 4;
	public static final int STORE_WORD_INTO_ADDRESS_AT_TEMP = 5;
	public static final int STORE_WORD_INTO_CONST_ADDRESS = 6;
	public static final int MOVE_TEMP_TO_TEMP = 7;
	public static final int LOAD_ADDRESS = 8;
	public static final int LOAD_FROM_TEMP_OFFSET_EXP_MEM = 9;
	
	
	private Vector srcList;
	private Vector destList;
	
	private String instructionDescription;
	
	private Integer immediateValue;
	private int operationCode;
	private String label;
	
	public Operation()
	{
		srcList = new Vector();
		destList = new Vector();
	}
	
	public Operation(Vector dest, Vector src, int operationCode, Integer immediateValue,
					 String label) {
		destList = dest;
		srcList = src;
		this.operationCode = operationCode;
		this.immediateValue = immediateValue;
		this.label = label;		
		generateMipsStyleCode();		
	}
	
	public void setDefsAndSources(Vector dest, Vector src){
		this.destList = dest;
		this.srcList = src;
	}
	
	private void generateMipsStyleCode(){
		
		Temp destTemp = null; // Only one destination
		if(destList != null){
			destTemp = (Temp) destList.elementAt(0); 
		}
		
		int numSrcTemps = 0;
		if(srcList != null){
			numSrcTemps = srcList.size();
		}
	
		if(operationCode == ADD || operationCode == MINUS || operationCode == MUL 
				|| operationCode == DIV){
			// A mathematical operation; should have one destination and two source 
			// variables - either two temporaries or a temporary and an immediate value
		
			String lhsOperand = ((Temp)srcList.elementAt(0)).toString();
			String rhsOperand = "";
			if(numSrcTemps == 2)
				rhsOperand = ((Temp)srcList.elementAt(1)).toString();
			else
				rhsOperand = "" + immediateValue.intValue();
			
			if(operationCode == ADD)
				instructionDescription = tab + "add " + destTemp.toString() + ", " + lhsOperand + ", " 
				+ rhsOperand + newLine;
			else if(operationCode == MINUS)
				instructionDescription = tab + "sub " + destTemp.toString() + ", " + lhsOperand + ", " 
				+ rhsOperand + newLine;
			else if(operationCode == MUL)
				instructionDescription = tab + "mul " + destTemp.toString() + ", " + lhsOperand + ", " 
				+ rhsOperand + newLine;
			else if(operationCode == DIV)
				instructionDescription = tab + "div " + destTemp.toString() + ", " + lhsOperand + ", " 
				+ rhsOperand + newLine;
		}
		else if(operationCode == LOAD_IMMEDIATE_VALUE){
			instructionDescription = tab + "li " + destTemp.toString() + ", " + 
			                 immediateValue.intValue() + newLine; 
		}
		else if(operationCode == STORE_WORD_INTO_ADDRESS_AT_TEMP){
			Temp srcTemp = (Temp) srcList.elementAt(0);
			Temp addrTemp = (Temp) srcList.elementAt(1);
			int offSet = immediateValue.intValue();
			String destStr = "";
			if(offSet == 0)
				destStr = "(" + addrTemp.toString() + ")" + newLine;
			else 
				destStr = offSet + "(" + addrTemp.toString() + ")" + newLine;
			instructionDescription = tab + "sw " + srcTemp.toString() + ", " + destStr;
		}
		else if(operationCode == STORE_WORD_INTO_CONST_ADDRESS){
			Temp srcTemp = (Temp) srcList.elementAt(0);
			// TODO - Check if the following statement is correct instruction
			instructionDescription = tab + "sw " + srcTemp.toString() + ", " + immediateValue.intValue() + newLine;
		}
		else if(operationCode == MOVE_TEMP_TO_TEMP){
			Temp srcTemp = (Temp) srcList.elementAt(0);
			// TODO - Check if the following statement is correct instruction
			instructionDescription = tab + "move " + destTemp.toString() + ", " + srcTemp.toString() 
										+ newLine;
		}
		else if(operationCode == LOAD_ADDRESS){
			if(label != null)                        // Load address from label
				instructionDescription = tab + "la " + destTemp.toString() + ", " + label + newLine;
			else if(immediateValue != null)          // Load address from constant value
				instructionDescription = tab + "la " + destTemp.toString() + ", " + immediateValue.intValue() + newLine;
			else                                     // Load address from register
			{
				Temp srcTemp = (Temp) srcList.elementAt(0);
				instructionDescription = tab + "la " + destTemp.toString() + ", " + "(" + srcTemp.toString() + ")" + newLine;
			}
		}
		else if(operationCode == LOAD_FROM_TEMP_OFFSET_EXP_MEM){
			int offSet = immediateValue.intValue();
			Temp srcTemp = (Temp) srcList.elementAt(0);
			String signStr = "";
			if(offSet < 0) {
				signStr = "-";
				int val = Math.abs(offSet);
				
				instructionDescription = tab + "lw " + destTemp.toString() + ", " +  signStr + val + "("  +
				srcTemp.toString()+ ")" + newLine;
			}
			else if(offSet > 0){
				instructionDescription = tab + "lw " + destTemp.toString() + ", " +  offSet + "("  +
				srcTemp.toString()+ ")" + newLine;
			}
			else
			{
				instructionDescription = tab + "lw " + destTemp.toString() + ", " + "("  +
				srcTemp.toString()+ ")" + newLine;
			}
		}
	}
	
	public String getAssemCode(boolean showUseDef)
	{
		if(!showUseDef)
			return instructionDescription;
		else
		{
			String comment = "				# Def = ";
			
			if(destList == null) comment += "None";
			else
			{
				int numDestTemps = destList.size();
				for(int i = 0; i < numDestTemps; i++)
				{
					comment += (Temp) destList.elementAt(i) + " ";
				}
			}
			
			comment += " Use = ";
			if(srcList == null) comment += "None";
			else
			{
				int numSrcTemps = srcList.size();
				for(int i = 0; i < numSrcTemps; i++)
				{
					comment += (Temp) srcList.elementAt(i) + " ";
				}
			}
			
			// Remove the newline at the end of assemcode
			int nlOffset = instructionDescription.lastIndexOf("\n");
			if(nlOffset > 0)
			{
				instructionDescription = instructionDescription.substring(0, nlOffset);
			}
			
			return instructionDescription + comment + "\n";
		}
	}
	
	public Vector getSrcList()
	{
		return srcList;
	}
	
	public Vector getDestList()
	{
		return destList;
	}
	
	public Vector getJumps()
	{
		return null;
	}
	
	public String getLabelStr()
	{
		return null;
	}
	
	public int getType()
	{
		return AssemblyInstruction.INSTRUCTION;
	}
	
	public boolean isMove()
	{
		return (operationCode == MOVE_TEMP_TO_TEMP);
	}
	
	public String getLabel(){
		return label;
	}
	
	public int getOperationCode(){
		return operationCode;
	}
	
	public Integer getImmediateValue(){
		return immediateValue;
	}
}
